#pragma once
/*
 * TCP net session for client.
 */
#include "log.h"
#include "default_interface.hpp"
#include "share_ptr_session.hpp"
#include "timer/time_wheel.h"
#include "handler.h"
#include "singleton.h"
#include "time.hpp"

// client session declaration.
class CCliSession;

// session & session factory.
using CSharePtrSession = anet::tcp::CSharePtrSession<CCliSession>;
using CSharePtrFactory = anet::tcp::CSharePtrSessionFactory<CCliSession>;
using CTimeWheelRegister = STimeWheelSpace::CTimerRegister;

// big codec.
using CBigCodec = anet::tcp::CBigCodec;

// session factory.
class CCliSessionFactory final : public CSharePtrFactory {
	DECLARE_SINGLETON(CCliSessionFactory);
protected:
	CCliSessionFactory() = default;
	virtual ~CCliSessionFactory() = default;
};


// client session.
class CCliSession final {
public:
	CCliSession() : m_close(true), m_pSession(nullptr), m_lastHeartBeatTime(0), m_uid(-1), m_id(0) {
	}
	virtual ~CCliSession() {
	}

	// timer id const.
	enum eTimer {
		EBeatHeartTimerId = 0,
		EBeatHeartTime = 20 * 1000,
	};

public:
    void SetUid(long long uid) {
		m_uid = uid;
	}
	long long GetUid() const {
		return m_uid;
	}

public:
	// template function interface.
	static CCliSession* Create() {
		return new CCliSession();
	}
	void OnRelease() {
		m_close = true;
		LogAInfo("%s:%d is released", m_pSession->getRemoteIP().c_str(), m_pSession->getRemotePort());
		delete this;
	}

	void SetSession(CSharePtrSession* pSession) {
		m_pSession = pSession;
	}
	void OnMessage(const char* msg, int len) {
		CClientHandler::Instance()->HandleMsg(this, msg, len);
	}
	void OnConnected() {
		m_close = false;
		LogAInfo("%s:%d is connected", m_pSession->getRemoteIP().c_str(), m_pSession->getRemotePort());

		// start beat heart check.
		this->startBeatHeartCheck();
	}
	void OnTerminate() {
		m_close = true;
		LogAInfo("%s:%d is terminated", m_pSession->getRemoteIP().c_str(), m_pSession->getRemotePort());
	}

	void SetId(unsigned int id) {
		m_id = id;
	}
	unsigned int GetId() const {
		return m_id;
	}

public:
	// send binary message.
	bool Send(const char* msg, unsigned int len) {
		if (msg == nullptr || len <= 0) {
			return false;
		}
		if (!m_close) {
		    m_pSession->Send(msg, len);
	    }
		return !m_close;
	}

	// send message with message id.
	bool Send(unsigned short msgId, const char* msg, unsigned int len) {
		if (!m_close) {
			m_pSession->Send(msgId, msg, len);
	    }
		return !m_close;
	}

	// remote_call is rpc call: method is remote function name, and args is function's parameter.
	template <typename... Args>
	bool remote_call(const std::string& method, Args&&... args) {
		if (!m_close) {
			m_pSession->remote_call(method, std::forward<Args>(args)...);
		}
		return !m_close;
	}

	// close connection.
	void Close() {
		if (!m_close) {
			m_pSession->Close();
		}
	}

	// beat heart.
	void SetBeatHeartTime(long long time) {
		m_lastHeartBeatTime = time;
	}
	long long GetBeatHeartTime() {
		return m_lastHeartBeatTime;
	}

protected:
	// beatHeartCheck beat heart check.
	void startBeatHeartCheck() {
		// set last heart beat time.
		this->SetBeatHeartTime(GetNowMSTime());

		// add timer to beat heart check.
		m_timer.add_repeated_timer([this](void* pData) {
			(void)pData;
			if (GetNowMSTime() - this->GetBeatHeartTime() > EBeatHeartTime) {
				LogAInfo("There is no beat heart for %s client", m_pSession->getRemoteIP().c_str());
				this->Close();
				this->m_timer.kill_timer(EBeatHeartTimerId);
			}
		}, EBeatHeartTimerId, EBeatHeartTime);
	}

private:
	// whether it is closed.
	bool m_close;

	// session proxy.
	CSharePtrSession* m_pSession;

	// timer register.
	CTimeWheelRegister m_timer;

	// last heart beat time.
	long long m_lastHeartBeatTime;

	// client/role uid.
	long long m_uid;

	// session id.
	unsigned int m_id;
};